import * as vscode from 'vscode';
import { WebviewLoader, XmlConverter, consts, logError, logInfo, getACoreIDEPath } from '../../utils';
import { readFileSync } from 'fs';
import { X2jOptions } from 'fast-xml-parser';

/** 通信用接口 */
interface TestMessage {
  /** 命令 */
  command: string;
  /** 数据 */
  text: string;
}
const buildMsg = (command: string, text = ''): TestMessage => {
  return {
    command,
    text
  };
};

const getCfgData = 'getCfgData';
const updateData = 'updateData';
const getCfgMetadata = 'getCfgMetadata';

export default class CfgEditorProvider implements vscode.CustomTextEditorProvider {
  constructor(private readonly context: vscode.ExtensionContext) {
    this.loader = WebviewLoader.getInstance(this.context.extensionUri);
  }

  private static readonly id = consts.ids.CFG_EDITOR_VIEW;
  private static readonly htmlPath = consts.templateFile.UI_ENTRY;
  private loader: WebviewLoader;
  private fileName = '';
  private debounceTimer: NodeJS.Timeout | null = null;
  private metaData: any;

  public static register(context: vscode.ExtensionContext): vscode.Disposable {
    const provider = new CfgEditorProvider(context);
    const providerRegistration = vscode.window.registerCustomEditorProvider(this.id, provider);
    return providerRegistration;
  }

  /**
   * 自定义编辑器的文件解析方法，启动自定义编辑器自动执行
   * @param document 文件对象
   * @param webviewPanel webview面板
   * @param _token 可取消对象
   */
  public async resolveCustomTextEditor(document: vscode.TextDocument, webviewPanel: vscode.WebviewPanel): Promise<void> {
    // Setup initial content for the webview
    webviewPanel.webview.options = {
      enableScripts: true
    };
    this.fileName = document.fileName;
    this.loader.setWebViewLocalContent(webviewPanel.webview, CfgEditorProvider.htmlPath);

    const updateWebview = () => {
      const text = this.generateCfgJsonStr(document);
      webviewPanel.webview.postMessage(buildMsg(getCfgData, text));
      // writeFileSync('F:\\zgling\\data\\osdata.json', text, { encoding: 'utf-8' });
    };

    // Hook up event handlers so that we can synchronize the webview with the text document.
    //
    // The text document acts as our model, so we have to sync change in the document to our
    // editor and sync changes in the editor back to the document.
    //
    // Remember that a single text document can also be shared between multiple custom
    // editors (this happens for example when you split a custom editor)

    const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => {
      if (e.document.uri.toString() === document.uri.toString()) {
        // 防抖
        if (this.debounceTimer) {
          clearTimeout(this.debounceTimer);
        }
        this.debounceTimer = setTimeout(() => {
          updateWebview();
          this.debounceTimer = null;
        }, 300);
      }
    });

    // Make sure we get rid of the listener when our editor is closed.
    webviewPanel.onDidDispose(() => {
      changeDocumentSubscription.dispose();
    });

    // Receive message from the webview.
    webviewPanel.webview.onDidReceiveMessage((e: TestMessage) => {
      switch (e.command) {
        case 'onloaded': {
          webviewPanel.webview.postMessage(buildMsg('navigateTo', '/cfgEditor'));
          return;
        }
        case getCfgData: {
          updateWebview();
          return;
        }
        case updateData: {
          const data = e.text;
          this.updateTextDocument(document, data);
          return;
        }
        case getCfgMetadata: {
          const metadata = this.generateMetaData(document);
          webviewPanel.webview.postMessage(buildMsg(getCfgMetadata, JSON.stringify(metadata)));
          return;
        }
        case 'getIdePath': {
          const idePath = getACoreIDEPath();
          // const idePath = vscode.workspace.getConfiguration(consts.PLUGIN_NAME).get(consts.configKeys.aCoreIdePath) as string;
          webviewPanel.webview.postMessage(buildMsg('getIdePath', idePath));
          return;
        }
        case 'openFile': {
          vscode.commands.executeCommand('vscode.openWith', vscode.Uri.file(e.text), 'default');
        }
      }
    });
  }

  private generateMetaData(document: vscode.TextDocument) {
    // step1： 获取cfg文件内容，并解析成json
    const cfgObj = XmlConverter.xmlToJson(document.getText()) as ICfgObj;

    // step2： 根据cfg json，遍历解析xsd文件，并构造metadata
    const metadata = {};
    this.getMetadataFromXsdFile(cfgObj.configure.root, metadata, 'root');
    this.metaData = metadata;
    // writeFileSync('F:\\zgling\\data\\osmd.json', JSON.stringify(metadata), { encoding: 'utf-8' });
    return metadata;
  }

  private generateCfgJsonStr(document: vscode.TextDocument) {
    const options: X2jOptions = {
      ignoreAttributes: false
    };
    const cfgToJson = XmlConverter.cfgToJson;
    // step1：获得cfg json对象
    const cfgJson = cfgToJson(document.getText(), options);
    if (!cfgJson) {
      return;
    }
    // step2：根据metadata，更新json对象，修正值的类型（有的需要转换为数组）
    const metadata = this.metaData;
    const data = cfgJson.configure;
    const handleNodeData = (nodeData: any, nodeMetaData: any) => {
      if (!nodeMetaData) {
        return;
      }
      const useArrayValue = nodeMetaData?.typeName === 'StructWithChildren';

      const childTags = Object.keys(nodeData).filter(item => !item.startsWith('@_'));
      if (useArrayValue && childTags.length) {
        // 当父节点typeName = StructWithChildren时，说明它只有一种类型的子节点
        const child = childTags[0];
        const value = nodeData[child];
        // 如果有值，但非数组类型
        if (value && !Array.isArray(nodeData[child])) {
          nodeData[child] = [value];
        }
      }

      childTags.forEach(child => {
        const childNodeMetaData = nodeMetaData[child];
        const childNodeData = nodeData[child];
        if (Array.isArray(childNodeData)) {
          childNodeData.forEach(item => {
            handleNodeData(item, childNodeMetaData);
          });
        } else {
          handleNodeData(childNodeData, childNodeMetaData);
        }
      });
    };
    handleNodeData(data, metadata);
    return JSON.stringify(cfgJson);
  }

  private loadXsd(path: string) {
    try {
      const xsdContent = readFileSync(path, { encoding: 'utf-8' });
      const xsdJson = XmlConverter.xmlToJson(xsdContent) as IXsdObj;
      const elements = xsdJson.schema.element;
      return elements;
    } catch (e) {
      logError(`解析xsd文件${path}失败，${(e as Error).message}`);
    }
  }

  /**
   * 查询cfgNode的元数据（metadata）, 即 xsd schema
   * 分两种情况
   * 1、cfgNode 有 id 或者 externalPath 属性，则从 id 或者 externalPath 属性对应的xsd文件查找
   * 2、cfgNode 没有 id 或者 externalPath 属性，则从父节点对应的xsd文件查找
   * @param cfgNode 要查找的cfg节点
   * @param metaDataParent cfgNode的元数据父节点
   * @param nodePath cfgNode节点的路径
   * @param elements cfgNode的父节点的xsd文件中element元素列表
   */
  private getMetadataFromXsdFile(cfgNode: TCfgNode, metaDataParent: any, nodePath: string, elements: IXsdEle[] | undefined = undefined) {
    const pathTokens = nodePath.split('.');
    const nodeName = pathTokens[pathTokens.length - 1];
    const findXsdSchema = (
      eleName: string,
      elements: IXsdEle[] | IXsdEle | undefined
    ): { ele: IXsdEle | undefined; elements: IXsdEle[] | IXsdEle | undefined } => {
      let ele: IXsdEle | undefined;
      if (!elements) {
        return { ele, elements };
      }
      if (Array.isArray(elements)) {
        // 首先查找有没有name属性值和待查节点名称一致的
        ele = elements.find(item => item['@_name'] === eleName);
        // 如果未找到，则查找ref属性值和待查节点名称一致的
        if (!ele) {
          ele = elements.find(item => item['@_ref'] === eleName);
        }
      } else {
        ele = (elements as IXsdEle)['@_name'] === eleName || (elements as IXsdEle)['@_ref'] === eleName ? elements : undefined;
      }
      if (ele) {
        const xsdPath = ele['@_path'];
        if (xsdPath) {
          const absPath = this.getXsdAbsolutePath(xsdPath);
          elements = this.loadXsd(absPath);

          const result = findXsdSchema(eleName, elements);
          ele = result.ele;
          elements = result.elements;
        }
      }

      return { ele, elements };
    };
    // 定义内部函数，用于从elements中查找eleName对应元素的元数据
    const getEleMetadata = (eleName: string, elements: IXsdEle[] | IXsdEle | undefined): any => {
      const data = findXsdSchema(eleName, elements);
      const ele = data.ele;
      elements = data.elements;
      if (!ele) {
        logError(`未找到${eleName}的xsd schema`);
        return;
      }
      const md: ICfgEleMetaData = {
        name: '',
        label: '',
        macro: '',
        desc: '',
        type: ''
      };
      md.name = ele?.['@_name'] || '';
      md.label = ele?.['@_label'] || '';
      md.macro = ele?.['@_macro'] || '';
      md.desc = ele?.annotation?.documentation || '';
      md.type = eleName;
      md.typeName = ele?.['@_typename'] || '';

      // 记录节点的依赖项
      if (ele?.['@_despendSchemaId']) {
        const depends = ele['@_despendSchemaId'].split(';').map(item => {
          const leftParenthesisIdx = item.indexOf('(');
          if (leftParenthesisIdx > -1) {
            return item.slice(0, leftParenthesisIdx);
          }
          return item;
        });
        md.despendSchemaId = depends;
      }

      // 记录节点使用哪个属性字段的值作为节点label
      if (ele?.annotation?.appInfo?.['meta.element']['@_labelAttribute']) {
        md.labelAttribute = ele?.annotation?.appInfo?.['meta.element']['@_labelAttribute'];
      }

      //记录节点都有哪些属性
      if (ele?.complexType?.attribute) {
        const attrs = ele?.complexType?.attribute || [];
        if (Array.isArray(attrs)) {
          attrs.forEach(attr => {
            const attrMd = getAttrMetadata(attr['@_name'], attrs);
            md.attributes = md.attributes || [];
            md.attributes.push(attrMd);
          });
        } else {
          const attrMd = getAttrMetadata(attrs['@_name'], [attrs]);
          md.attributes = md.attributes || [];
          md.attributes.push(attrMd);
        }
      }

      // 节点的子类型是否是互斥的，即只能从中使能一个
      if (ele?.complexType?.choice) {
        const cElements = ele.complexType.choice.element;
        if (Array.isArray(cElements)) {
          md.choice = cElements.map(cEle => (cEle['@_name'] as string) || (cEle['@_ref'] as string));
        } else {
          md.choice = [(cElements['@_name'] as string) || (cElements['@_ref'] as string)];
        }
      }

      // 如果没有子节点
      if (!ele?.complexType?.sequence && !ele?.complexType?.choice) {
        md.isLeaf = true;
      }
      return { md, elements };
    };

    // 定义内部函数，用于查找attribute的元数据
    const getAttrMetadata = (attrName: string, attrs: IAttr[]) => {
      const attr = attrs.find(item => item['@_name'] === attrName);
      const md: ICfgAttrMetadata = {
        name: '',
        type: '',
        use: '',
        value: '',
        label: '',
        desc: ''
      };
      Object.keys(attr || {}).forEach(key => {
        if (key.startsWith('@_')) {
          const k = key.slice(2);
          const value = attr ? attr[key] || '' : '';
          md[k] = value as string;
        }
      });
      md.desc = attr?.annotation?.documentation || '';
      if (attr?.simpleType?.restriction) {
        md.type = md.type || attr?.simpleType?.restriction['@_base'] || '';
        md.options = [];
        let enums = attr?.simpleType?.restriction?.enumeration || [];
        if (!Array.isArray(enums)) {
          // xsd有可能可选项只定义了一个选项
          enums = [enums];
        }
        enums.forEach(e => (md.options as string[]).push(e['@_value']));
        if (!md.options.length) {
          delete md.options;
        }
      } else if (attr?.['@_type'].toLowerCase() === 'boolean') {
        md.options = [];
        md.options.push('true');
        md.options.push('false');
      }
      return md;
    };

    // 父节点的metadata是StructWithChildren类型，表明，在cfg中父节点的值是一个数组，数组的类型应该是一致的
    if (metaDataParent && metaDataParent.typeName === 'StructWithChildren') {
      // 调试时发现，有时候值为数组，有时候为对象
      cfgNode = Array.isArray(cfgNode) ? (cfgNode as unknown as TCfgNode[])[0] : cfgNode;
    }

    let xsdPath = cfgNode['@_externalPath'] || cfgNode['@_id'];
    // 第一种情况：cfgNode 有 id 或者 externalPath 属性
    // externalPath属性的作用是: cfg节点及其没有externalPath属性的子节点的元数据都应在externalPath指定的xsd文件中定义.
    // 从对应的xsd文件更新elements
    if (xsdPath && xsdPath.indexOf('/') > -1) {
      const absPath = this.getXsdAbsolutePath(xsdPath);
      elements = this.loadXsd(absPath) || [];
    } else {
      // 如果cfg节点上没有指定xsd schema文件路径，说明当前cfg节点的schema已包含在父节点的schema中。
      // 所以先找到父节点的schema，在从它的子节点schema列表中查找当前cfg节点的schema
      let parentEle: IXsdEle | undefined;
      if (Array.isArray(elements)) {
        parentEle = elements.find(item => item['@_name'] === metaDataParent.name);
      } else if (elements) {
        const eleName = (elements as IXsdEle)['@_name'];
        parentEle = eleName === metaDataParent.name ? elements : undefined;
      }
      if (parentEle) {
        // 从父节点的子节点列表查找
        let ele: IXsdEle | undefined;
        if (Array.isArray(parentEle.complexType.sequence?.element)) {
          ele = parentEle.complexType.sequence?.element.find(item => item['@_name'] === nodeName || item['@_ref'] === nodeName);
        } else {
          const childEle = parentEle.complexType.sequence?.element;
          if (childEle) {
            ele = childEle['@_name'] === nodeName || childEle['@_ref'] === nodeName ? childEle : undefined;
          }
        }
        if (ele) {
          xsdPath = ele['@_path'];
          if (xsdPath) {
            xsdPath = this.getXsdAbsolutePath(xsdPath);
            elements = this.loadXsd(xsdPath);
          }
        }
      }
    }
    const curdata = getEleMetadata(nodeName, elements);
    const curEleMetadata = curdata.md;
    elements = curdata.elements;
    if (curEleMetadata) {
      metaDataParent[nodeName] = curEleMetadata;
    }

    if (Array.isArray(cfgNode)) {
      cfgNode = cfgNode[0];
    }

    // 遍历cfgNode的子节点，并将其元数据添加到元数据对象
    Object.keys(cfgNode || {}).forEach(item => {
      if (!item.startsWith('@_')) {
        logInfo(`查询cfg节点${item}的xsd schema`);
        this.getMetadataFromXsdFile(cfgNode[item] as TCfgNode, metaDataParent[nodeName], item, elements);
      }
    });
  }

  private getXsdAbsolutePath(relativePath: string) {
    const aCoreIdePath = getACoreIDEPath();
    // const aCoreIdePath = vscode.workspace.getConfiguration(consts.PLUGIN_NAME).get(consts.configKeys.aCoreIdePath) as string;
    return `${aCoreIdePath}/target/${relativePath}`;
  }

  /**
   * 往源文件写回json.
   * @param document 文件对象
   * @param jsonStr json 对象
   * @returns 是否成功
   */
  private updateTextDocument(document: vscode.TextDocument, jsonStr: string) {
    // 防抖
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
    }
    this.debounceTimer = setTimeout(() => {
      this.debounceTimer = null;
      const edit = new vscode.WorkspaceEdit();

      // Just replace the entire document every time for this example extension.
      // A more complete extension should compute minimal edits instead.
      const cfg = XmlConverter.osJsonToCfg(jsonStr);
      if (!cfg) {
        return;
      }
      edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), cfg);
      return vscode.workspace.applyEdit(edit);
    }, 300);
  }
}
